package DynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;

interface MoveAble{
    void move();
    void test();
}

class Tank implements MoveAble{

    @Override
    public void move() {
        try {
            Thread.sleep(new Random().nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println("calcal");
        }
    }

    @Override
    public void test() {

    }
}

class LogProxy implements MoveAble{
    MoveAble m;
    public LogProxy(MoveAble m){
        this.m = m;
    }
    @Override
    public void move() {
        System.out.println("log");
        m.move();
    }

    @Override
    public void test() {

    }
}

/**
 * TimeTankProxy 可以代理任何对象 而不是单单的movable。
 * 本质是分离行为与代理 对象
 * 动态代理
 */
public class Dynamic {
    public static void main(String[] args){

        // instrument 也可以动态代理 java 自带 本质钩子函数拦截器 任意 class load到内存中会做拦截。 这个可以完全控制二进制 这种必须要了解整个class内容非常繁琐。
        // asm 只能通过它的api
        // cglib动态代理 这个生成要比jdk的简单
        /**
         * cglib方式 Tank 可以不实现任何接口
         * 配置maven
         * cglib
         * 类如果是final 是不能被生成动态代理类的 final不能被继承，但是底层用的是asm。
         *
         * final 的用asm 生成
         */

        // 动态 动态修改类的内容 - asm
        // asm操纵二进制  改的是class 里面的16进制
        /**
         * cafe babe 代表java的class
         * 理论上任何语言都可以编译成class 扔给jvm 处理
         */

        // jdk的proxy反射必须面向接口，代理类也要实现接口。

        // 从源码中得知，设置这个值，可以把生成的代理类，输出出来 InvocationHandler的匿名累并没真是存在磁盘
        // 打开这个可以看到存在存盘上 可以看到在根上生成 $Proxy0.class 中间类不生成就在内存中 $Proxy0.class 是反编译出来的
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

        // 动态代理利用发射实现
        // Tank.class.getClassLoader() 类 load到内存这个时候不需要看到Tank的源代码只知道有这么个类就行。 分析字节码就知道有哪写属性
        // Proxy.newProxyInstance 返回代理对象 动态生成的代理对象。 类也是动态生成的。 动态生成代理类并且返回代理对象。
        // 参数1 用哪个loader把代理对象load到内存，和被代理用一个loader就行。 参数2 代理对象应该实现哪些接口 因为一个类可以实现多个接口
        // 所以是接口数组
        // 参数3 InvocationHandler 调用处理器 被代理对象的那个方法被调用我们的方法怎么处理。 匿名类
        // InvocationHandler 做代理的时候指定自己的操作
        /**
         * 加载器的内容
         * 树形结构
         * bootstrap class loader
         *     ext class loader
         *         app class loader
         *            自定义 class loader
         *            a        b
         *            这里 假如a是被代理对象 b是和a平行的 这里 b的classLoader是不可以替换a的但是父辈是可以的 也就是 app class loader
         *            可以替换Tank.class.getClassLoader() b是不可以的 一般用的就是被代理类的加载器
         *
         *            说白了其实就是用 Tank class loader 把 InvocationHandler 接口的实现类加到内存然后实例化。
         *            InvocationHandler 就是代理类
         */
        MoveAble m = (MoveAble)Proxy.newProxyInstance(Tank.class.getClassLoader(), new Class[]{MoveAble.class},
                new InvocationHandler() {
                    public void before(){
                        System.out.println("start");
                    }
                    public void after(){
                        System.out.println("end");
                    }
            /**
             * invoke 会在执行任何方法的时候被调用 invoke 的调用时机在 jvm中调用的。
             *
             * @param proxy 这个是代理对象就是 内存里面的 proxy0.class 生成的代理对象
             * @param method 哪个方法被调用的时候  这个参数直接告诉你调用了哪个方法 method.getName() 获取调用了哪个方法  getClass().getMethods() 获取所有方法
             * @param args 方法调用的时候需要的参数 m.move() 里面的参数会透传到这里
             *
             * @return
             * @throws Throwable
             */

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                before();
                after();
                // 这里相当于调用的是 Tank 的move 类似js的call
                return method.invoke(new Tank(), args);
            }
        });

        /**
         * 调用了move 是怎么调用了 invoke 方法
         */
        m.move(); // move 经过了一些路径最后调用了 invoke 方法
        // 通过生成的代理累可以看到 move 的调用直到 invoke



    }
}
